package ffte.core.mixed

import spinal.core._
import spinal.lib._

import ffte.types.VecComplex
import ffte.property.{getFFTIW,getFFTOW,getShiftMethod}
import ffte.core.streamed.{streamedComponents}
import ffte.core.mixed.{Mixed}
import ffte.core.{widthed,StreamedGroupConfig}

/**
 * Factory object for Forward Mixed FFT (MFFT) implementations
 *
 * Provides convenient factory methods for creating forward mixed-radix FFT components
 * with automatic scaling and width configuration. This factory simplifies the
 * instantiation of mixed FFT components for common use cases.
 *
 * Mixed Architecture Benefits:
 * - Combines flat and streamed implementations optimally
 * - Reduced resource usage compared to fully streamed
 * - Better performance than fully flat implementations
 * - Adapts architecture strategy per computational stage
 *
 * Scaling Strategy:
 * The factory automatically calculates appropriate scaling to prevent
 * overflow in fixed-point arithmetic while maintaining precision:
 * - Base scaling from getShiftMethod(N*G) for total transform size
 * - Additional scaling if input width > output width (dW > oW)
 * - Total scaling ensures safe fixed-point computation
 *
 * Factory Methods:
 * - apply(G,N): Automatic scaling with global widths
 * - apply(G,N,S): Custom scaling with global widths
 * - apply(G,N,S,_dW): Custom scaling with unified width
 * - apply(G,N,S,_dW,_oW): Full customization
 *
 * @example
 * // Create 64-point FFT with 8-sample groups and automatic scaling
 * val fft64 = MFFT(8, 64)
 *
 * // Create 128-point FFT with 16-sample groups and custom scaling
 * val fft128 = MFFT(16, 128, 8)
 */
object MFFT extends widthed {
    /**
     * Create FFT with automatic scaling and global widths
     *
     * @param G Group size for parallel processing
     * @param N Transform size
     * @return Forward mixed FFT component with automatic configuration
     */
    def apply(G: Int, N: Int) = {
        val s = getShiftMethod(N * G) + (if (dW > oW) (dW - oW) else 0)
        new dft(G, N, s, dW, oW)
    }

    /**
     * Create FFT with custom scaling and global widths
     *
     * @param G Group size for parallel processing
     * @param N Transform size
     * @param S Custom scaling shift amount
     * @return Forward mixed FFT component with specified scaling
     */
    def apply(G: Int, N: Int, S: Int) = new dft(G, N, S, dW, oW)

    /**
     * Create FFT with custom scaling and unified width
     *
     * @param G Group size for parallel processing
     * @param N Transform size
     * @param S Custom scaling shift amount
     * @param _dW Unified data width for both input and output
     * @return Forward mixed FFT component with unified width
     */
    def apply(G: Int, N: Int, S: Int, _dW: Int) = new dft(G, N, S, _dW, _dW)

    /**
     * Create FFT with full customization
     *
     * @param G Group size for parallel processing
     * @param N Transform size
     * @param S Custom scaling shift amount
     * @param _dW Input data width
     * @param _oW Output data width
     * @return Forward mixed FFT component with full customization
     */
    def apply(G: Int, N: Int, S: Int, _dW: Int, _oW: Int) = new dft(G, N, S, _dW, _oW)
}

/**
 * Factory object for Inverse Mixed FFT (MIFFT) implementations
 *
 * Provides convenient factory methods for creating inverse mixed-radix FFT components.
 * The inverse FFT computes the inverse Discrete Fourier Transform, converting
 * frequency domain data back to time domain using mixed architecture.
 *
 * Mathematical Foundation:
 * The inverse FFT uses the same computational structure as the forward FFT
 * but with conjugated twiddle factors. In fixed-point arithmetic, this is
 * implemented by swapping real and imaginary components and using
 * the same computational kernel.
 *
 * Factory Methods:
 * Same method signatures as MFFT but with inv=true parameter
 *
 * @example
 * // Create 64-point inverse FFT with 8-sample groups
 * val ifft64 = MIFFT(8, 64)
 */
object MIFFT extends widthed {
    /**
     * Create inverse FFT with automatic scaling and global widths
     *
     * @param G Group size for parallel processing
     * @param N Transform size
     * @return Inverse mixed FFT component with automatic configuration
     */
    def apply(G: Int, N: Int) = {
        val s = getShiftMethod(N * G) + (if (dW > oW) (dW - oW) else 0)
        new dft(G, N, s, dW, oW, true)
    }

    /**
     * Create inverse FFT with custom scaling and global widths
     *
     * @param G Group size for parallel processing
     * @param N Transform size
     * @param S Custom scaling shift amount
     * @return Inverse mixed FFT component with specified scaling
     */
    def apply(G: Int, N: Int, S: Int) = new dft(G, N, S, dW, oW, true)

    /**
     * Create inverse FFT with custom scaling and unified width
     *
     * @param G Group size for parallel processing
     * @param N Transform size
     * @param S Custom scaling shift amount
     * @param _dW Unified data width for both input and output
     * @return Inverse mixed FFT component with unified width
     */
    def apply(G: Int, N: Int, S: Int, _dW: Int) = new dft(G, N, S, _dW, _dW, true)

    /**
     * Create inverse FFT with full customization
     *
     * @param G Group size for parallel processing
     * @param N Transform size
     * @param S Custom scaling shift amount
     * @param _dW Input data width
     * @param _oW Output data width
     * @return Inverse mixed FFT component with full customization
     */
    def apply(G: Int, N: Int, S: Int, _dW: Int, _oW: Int) = new dft(G, N, S, _dW, _oW, true)
}

/**
 * Standard mixed-radix DFT implementation
 *
 * This class implements a mixed-radix DFT using the standard Mixed.gen decomposition
 * strategy. It combines flat and streamed architectures to optimize resource usage
 * while maintaining good performance characteristics.
 *
 * Architecture Strategy:
 * - Uses Mixed.gen() for standard factor ordering (flat first, streamed second)
 * - Processes data in groups of size G for parallel efficiency
 * - Supports both forward and inverse transforms
 *
 * @param g Group size for parallel processing
 * @param n Number of groups (total size = g * n)
 * @param S Scaling shift amount for fixed-point overflow prevention
 * @param dW Input data width in bits
 * @param oW Output data width in bits
 * @param inv Whether to perform inverse transform (true=IFFT, false=FFT)
 */
class dft(g: Int, n: Int, val S: Int, val dW: Int, val oW: Int, inv: Boolean = false) extends streamedComponents {
    /** Group size for streaming interface */
    override val G = g

    /** Input permutation table for debugging/verification */
    val in_tab = Array.ofDim[Int](n, g)

    /** Output permutation table for debugging/verification */
    val out_tab = Array.ofDim[Int](n, g)

    /** Standard streaming I/O interface */
    val io = iogen

    /**
     * Hardware implementation area with group configuration
     *
     * Creates the mixed FFT implementation using standard decomposition
     * and handles the interface between streaming and mixed domains.
     */
    val gArea = StreamedGroupConfig(g) on new Area {
        /** Generate mixed FFT decomposition for total transform size */
        val gp = Mixed.gen(n * g)

        /** Create mixed FFT implementation method */
        val m = new Mixed(S)

        /** Instantiate mixed FFT component */
        val u = m(gp.fft, "top")

        /** Connect I/O with optional inverse transformation */
        u.io.d <> (if (inv) streamedComponents.inv(io.d) else io.d)
        u.io.q <> (if (inv) streamedComponents.inv(io.q) else io.q)

        /** Build permutation tables for debugging */
        for (i <- 0 until n; k <- 0 until g) {
            in_tab(i)(k) = gp.load_tab(i * g + k)
            out_tab(i)(k) = gp.store_tab(i + k * n)
        }
    }

    /** Component delay in clock cycles */
    def delay = gArea.u.delay
}

/**
 * Reverse mixed-radix DFT implementation
 *
 * This class implements a mixed-radix DFT using the reverse Mixed.rgen decomposition
 * strategy. The reverse approach swaps the order of flat and streamed implementations
 * for different performance characteristics.
 *
 * Architecture Strategy:
 * - Uses Mixed.rgen() for reverse factor ordering (streamed first, flat second)
 * - Processes data in groups of size G for parallel efficiency
 * - Different performance trade-offs compared to standard dft
 *
 * @param g Group size for parallel processing
 * @param n Number of groups (total size = g * n)
 * @param S Scaling shift amount for fixed-point overflow prevention
 * @param dW Input data width in bits
 * @param oW Output data width in bits
 * @param inv Whether to perform inverse transform (true=IFFT, false=FFT)
 */
class rdft(g: Int, n: Int, val S: Int, val dW: Int, val oW: Int, inv: Boolean = false) extends streamedComponents {
    /** Group size for streaming interface */
    override val G = g

    /** Input permutation table for debugging/verification */
    val in_tab = Array.ofDim[Int](n, g)

    /** Output permutation table for debugging/verification */
    val out_tab = Array.ofDim[Int](n, g)

    /** Standard streaming I/O interface */
    val io = iogen

    /**
     * Hardware implementation area with group configuration
     *
     * Creates the mixed FFT implementation using reverse decomposition
     * and handles the interface between streaming and mixed domains.
     */
    val gArea = StreamedGroupConfig(g) on new Area {
        /** Generate reverse mixed FFT decomposition for total transform size */
        val gp = Mixed.rgen(n * g)

        /** Create mixed FFT implementation method */
        val m = new Mixed(S)

        /** Instantiate mixed FFT component */
        val u = m(gp.fft, "top")

        /** Connect I/O with optional inverse transformation */
        u.io.d <> (if (inv) streamedComponents.inv(io.d) else io.d)
        u.io.q <> (if (inv) streamedComponents.inv(io.q) else io.q)

        /** Build permutation tables for debugging (note swapped indexing) */
        for (i <- 0 until n; k <- 0 until g) {
            in_tab(i)(k) = gp.load_tab(i + k * n)
            out_tab(i)(k) = gp.store_tab(i * g + k)
        }
    }

    /** Component delay in clock cycles */
    def delay = gArea.u.delay
}